#include "server_detect.h"
#include <event2/event-config.h>

/* Compatibility for possible missing IPv6 declarations */
#include "ipv6-internal.h"
#include "utility.h"

#include <sys/types.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <event2/event.h>
#include <event2/dns.h>
#include <event2/dns_struct.h>
#include <event2/util.h>

#ifdef _EVENT_HAVE_NETINET_IN6_H
#include <netinet/in6.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#define u32 ev_uint32_t
#define u8 ev_uint8_t

int dns_server_count = 0;

char *
sock_ntop_host(const struct sockaddr *sa, socklen_t salen);

static void
evdns_server_callback(struct evdns_server_request *req, void *data);

static int
get_local_ip(char *ip);
/**
  * @brief  创建UDP服务器，接受广播并返回TCP与UDP服务的地址与端口
  * @note   
  * @param  *arg: 暂未用到
  * @retval None: NULL
  */
void *create_file_dns_server(void *arg)
{
    struct event_base *event_base = NULL;
    event_base = event_base_new();

    /* 1.创建UDP套接字 */
    evutil_socket_t sock;
    struct sockaddr_in my_addr;
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        perror("socket");
        exit(1);
    }
    evutil_make_socket_nonblocking(sock);

    /* 2.绑定地址 */
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(FILE_TRANS_DNS_PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
    {
        perror("bind");
        exit(1);
    }
    evdns_add_server_port_with_base(event_base, sock, 0, evdns_server_callback, NULL);

    printf("DNS 解析已开启(服务器发现)\n");

    /* 3. 循环监听*/
    fflush(stdout);
    event_base_dispatch(event_base);
    return NULL;
}

/**
 * @brief  DNS请求回调
 * !@note   只回答EVDNS_TYPE_A类型的请求
 * @param  *req: 
 * @param  *data: 
 * @retval None
 */
static void
evdns_server_callback(struct evdns_server_request *req, void *data)
{
    int i, r;
    (void)data;

    /* 1.获取本地IP地址 */
    char ip[INET_ADDRSTRLEN];
    if (get_local_ip(ip) != SUCCESS)
    {
        printf("can not get local ip\n");
        exit(0);
    }

    for (i = 0; i < req->nquestions; ++i)
    {
        in_addr_t ans = inet_addr(ip);
        uint32_t port = htonl(FILE_TRANS_TCP_PORT);
        uint32_t port2 = htonl(FILE_TRANS_UDP_PORT);
        if (req->questions[i]->type == EVDNS_TYPE_A &&
            req->questions[i]->dns_question_class == EVDNS_CLASS_INET &&
            memcmp(req->questions[i]->name,SERVER_DOMAIN_NAME,sizeof(SERVER_DOMAIN_NAME)) == 0)
        {
            printf(" -- replying for %s (A)\n", req->questions[i]->name);
            r = evdns_server_request_add_a_reply(req, req->questions[i]->name, 1, &ans, 10);
            assert(r >= 0);
            r = evdns_server_request_add_a_reply(req, "port", 1, &port, 10);
            assert(r >= 0);
            r = evdns_server_request_add_a_reply(req, req->questions[i]->name, 1, &ans, 10);
            assert(r >= 0);
            r = evdns_server_request_add_a_reply(req, "port", 1, &port2, 10);
            assert(r >= 0);
        }
        else
        {
            printf(" -- skipping %s [%d %d]\n", req->questions[i]->name,
                   req->questions[i]->type, req->questions[i]->dns_question_class);
        }
    }

    r = evdns_server_request_respond(req, 0);
    if (r < 0)
        printf("eeek, couldn't send reply.\n");
}
/**
 * @brief  获取本地IP地址
 * @note   
 * @param  *ip: 一个合法地址，至少是16字节长，用于保存获取的IP
 * @retval 
 */
static int get_local_ip(char *ip)
{
    assert(ip != NULL);
    int fd, intrface, retval = 0;
    struct ifreq buf[INET_ADDRSTRLEN];
    struct ifconf ifc;
    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
    {
        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = (caddr_t)buf;
        if (!ioctl(fd, SIOCGIFCONF, (char *)&ifc))
        {
            intrface = ifc.ifc_len / sizeof(struct ifreq);
            while (intrface-- > 0)
            {
                if (!(ioctl(fd, SIOCGIFADDR, (char *)&buf[intrface])))
                {
                    strncpy(ip, inet_ntoa(((struct sockaddr_in *)(&buf[intrface].ifr_addr))->sin_addr), INET_ADDRSTRLEN);
                    if (memcmp(ip, "127.0.0.1", sizeof("127.0.0.1")))
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            retval = -1;
        }
    }
    else
    {
        retval = -2;
    }
    close(fd);
    return retval;
}

/**
 * @brief  从www.fileTransServer.com转换到3www5fileTransServer3com
 * @note   
 * @param  dns: 保存转换后的名称
 * @param  host: 需要被转换的名称
 * @retval None
 */
static void change_to_dns_name_format(unsigned char *dns, const unsigned char *host)
{
    int lock = 0, i;
    char hostIp[128];
    memcpy(hostIp, host, strlen((char *)host) + 1);
    strcat((char *)hostIp, ".");

    for (i = 0; i < strlen((char *)hostIp); i++)
    {
        if (hostIp[i] == '.')
        {
            *dns++ = i - lock;
            for (; lock < i; lock++)
            {
                *dns++ = hostIp[lock];
            }
            lock++;
        }
    }
    *dns++ = '\0';
}

/**
 * @brief  初始化话dns报头
 * @note   
 * @param  *struDnsHead: 
 * @retval 成功返回0
 */
static int init_dns_head(struct DNS_HEADER *struDnsHead)
{
    assert(struDnsHead != NULL);
    struDnsHead->id = (unsigned short)htons(getpid()); //id设为进程标识符
    struDnsHead->qr = 0;                               //查询
    struDnsHead->opcode = 0;                           //标准查询
    struDnsHead->aa = 0;                               //不授权回答
    struDnsHead->tc = 0;                               //不可截断
    struDnsHead->rd = 1;                               //期望递归
    struDnsHead->ra = 0;                               //不可用递归
    struDnsHead->z = 0;                                //必须为0
    struDnsHead->ad = 0;
    struDnsHead->cd = 0;
    struDnsHead->rcode = 0;          //没有差错
    struDnsHead->q_count = htons(1); //1个问题
    struDnsHead->ans_count = 0;
    struDnsHead->auth_count = 0;
    struDnsHead->add_count = 0;
    return 0;
}

/**
 * @brief  分析DNS应答报文
 * !@note  服务器必须要给出4条回答,即IP,TCP port,IP UDP port 
 * @param  *buf: 待分析的字符
 * @param  *queryDomainName: 请求的域名
 * @param  *servAddr: 保存解析后的服务器地址
 * @retval 成功返回0
 */
static int analysis_dns_message(uint8_t *buf, uint8_t *queryDomainName,
                              struct sockaddr_in *servAddr)
{
    assert(servAddr != NULL);
    int answerNum = 0;
    int i = 0, j = 0; //循环变量
    struct RES_RECORD *pStruAnswers = NULL;
    struct DNS_HEADER *struDnsHead = (struct DNS_HEADER *)buf;
    uint8_t *reader = &buf[sizeof(struct DNS_HEADER) + (strlen((const char *)queryDomainName) + 1) + sizeof(struct QUESTION)];
    reader = reader + sizeof(short); //这时reader指向回答区域的查询类型字段

    answerNum = ntohs(struDnsHead->ans_count); //记录回答的条数
    if (answerNum > 0xff)
    {
        perror("to much answer\n"); //正常不会有这么多条回答
        exit(-1);
    }
    pStruAnswers = (struct RES_RECORD *)malloc(answerNum * sizeof(struct RES_RECORD));
    assert(pStruAnswers != NULL);

    for (i = 0; i < answerNum; i++)
    {
        pStruAnswers[i].resource = (struct R_DATA *)(reader);
        reader = reader + sizeof(struct R_DATA); //指向回答问题区域的资源数据字段
        //printf("addr: %x\n",(long)(char *)reader - (long)(char *)buf);
        if (ntohs(pStruAnswers[i].resource->type) != EVDNS_TYPE_A)
        {
            printf("解析DNS报文时遇到错误：not a EVDNS_TYPE_A type\n");
            answerNum = i;
            break;
        }
        pStruAnswers[i].rdata = NULL;
        pStruAnswers[i].rdata = (unsigned char *)malloc(ntohs(pStruAnswers[i].resource->data_len) + 1); //资源数据
        assert(pStruAnswers[i].rdata != NULL);
        for (j = 0; j < ntohs(pStruAnswers[i].resource->data_len); j++)
        {
            pStruAnswers[i].rdata[j] = reader[j];
        }
        pStruAnswers[i].rdata[ntohs(pStruAnswers[i].resource->data_len)] = '\0';
        reader = reader + ntohs(pStruAnswers[i].resource->data_len) + sizeof(short); //指向下一个回答问题区域的资源数据字段
        if (i == 0)
            reader += 4; //第一个应答数据后面多了四个字节??
    }
    /* 7.打印结果 */
    for (i = 0; (i + 3) < answerNum; i += 4)
    {
        if (ntohs(pStruAnswers[i].resource->type) == EVDNS_TYPE_A)
        {
            servAddr[0].sin_addr.s_addr = *(uint32_t *)pStruAnswers[i].rdata;
            servAddr[0].sin_port = htons((uint16_t)ntohl(*(uint32_t *)pStruAnswers[i + 1].rdata));
            servAddr[1].sin_addr.s_addr = *(uint32_t *)pStruAnswers[i + 2].rdata;
            servAddr[1].sin_port = htons((uint16_t)ntohl(*(uint32_t *)pStruAnswers[i + 3].rdata));
            printf("\n服务器地址:%s\nTCP端口:%ld\n\n", inet_ntoa(servAddr[0].sin_addr), (long int)ntohs(servAddr[0].sin_port));
            printf("\n服务器地址:%s\nUDP端口:%ld\n\n", inet_ntoa(servAddr[1].sin_addr), (long int)ntohs(servAddr[1].sin_port));
        }
    }
    /* 8.释放资源 */
    for (i = 0; i < answerNum; i++)
    {
        if (pStruAnswers[i].rdata != NULL)
        {
            free(pStruAnswers[i].rdata);
            pStruAnswers[i].rdata = NULL;
        }
    }
    if (pStruAnswers != NULL)
    {
        free(pStruAnswers);
        pStruAnswers = NULL;
    }
    return 0;
}

/**
 * @brief  查询文件服务器的地址 和 端口，通过广播DNS报文的方式
 * !@note  2秒内未检测到将退出本进程 
 * @param  *host: 服务器域名，如："www.fileTransServer.com"
 * @param  query_type: DNS查询类型,如 query_type=1
 * @retval None
 */
void get_server_by_name(char *host, int query_type, struct sockaddr_in *servAddr)
{
    assert(servAddr != NULL);
    const int32_t on = 1;
    uint8_t buf[65536];
    uint8_t *queryDomainName = NULL;
    int32_t sockfd;

    struct sockaddr_in dest;               //广播地址
    struct DNS_HEADER *struDnsHead = NULL; //DNS报头
    struct QUESTION *queryInfo = NULL;     //DNS查询信息

    /* 1.填写广播地址 */
    printf("\n正在查询聊天服务器的地址：%s\n", host);
    sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //建立分配UDP套结字
    if (sockfd == -1)
    {
        perror("socket()");
        exit(1);
    }
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0)
    {
        perror("setsockopt()");
        exit(1);
    }
    dest.sin_family = AF_INET;                           //IPv4
    dest.sin_port = htons(FILE_TRANS_DNS_PORT);          //53号端口
    dest.sin_addr.s_addr = inet_addr("255.255.255.255"); //DNS服务器IP

    /* 2.填写DNS报头 */
    struDnsHead = (struct DNS_HEADER *)&buf;
    init_dns_head(struDnsHead);

    /* 3.在问题区域填写文件服务器的域名 */
    queryDomainName = (unsigned char *)&buf[sizeof(struct DNS_HEADER)];
    change_to_dns_name_format(queryDomainName, (unsigned char *)host);                                            //修改域名格式
    queryInfo = (struct QUESTION *)&buf[sizeof(struct DNS_HEADER) + (strlen((const char *)queryDomainName) + 1)]; //queryInfo指向问题查询区域的查询类型字段

    queryInfo->qtype = htons(query_type); //查询类型为EVDNS_TYPE_A
    queryInfo->qclass = htons(1);         //查询类为1

    /* 4.向DNS服务器发送DNS请求报文 */
    if (sendto(sockfd, (char *)buf, sizeof(struct DNS_HEADER) + (strlen((const char *)queryDomainName) + 1) + sizeof(struct QUESTION),
               0, (struct sockaddr *)&dest, sizeof(dest)) < 0)
    {
        perror("sendto");
        exit(-1);
    }

    /* 5.从DNS服务器接受DNS响应报文 */
    int len = sizeof dest;
    fd_set rfds;
    struct timeval two_second = {2, 0};
    FD_ZERO(&rfds);
    FD_SET(sockfd, &rfds);
    int err = select(sockfd + 1, &rfds, NULL, NULL, &two_second);
    if (err == -1)
    {
        perror("select()");
        exit(-1);
    }
    int data_len = 0;
    if (FD_ISSET(sockfd, &rfds))
    {
        data_len = recvfrom(sockfd, (char *)buf, 65536, 0, (struct sockaddr *)&dest, (socklen_t *)&len);
    }
    else
    {
        printf("查询超时：请确保网络通畅且服务器正常开启\n\n");
        exit(-1);
    }
    if (data_len < 0)
    {
        perror("recvfrom");
        exit(-1);
    }

    /* 6.解析报文 */
    close(sockfd);
    analysis_dns_message(buf, queryDomainName, servAddr);
    return;
}

/**
 * @brief  地址转换，sockaddr地址转换为点分十进制地址
 * !@note  对inet_ntop进行封装是为了方便后期移植到IPv6
 * @param  *sa: 地址结构体
 * @param  salen: 结构体大小
 * @retval 
 */
char *
sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
{
    static char str[128]; /* Unix domain is largest */

    switch (sa->sa_family)
    {
    case AF_INET:
    {
        struct sockaddr_in *sin = (struct sockaddr_in *)sa;

        if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
            return (NULL);
        return (str);
    }

    default:
        snprintf(str, sizeof(str), "sock_ntop_host: unknown AF_xxx: %d, len %d",
                 sa->sa_family, salen);
        return (str);
    }
    return (NULL);
}